use errno::{errno, set_errno};
use nix::sys::signal;
use nix::sys::wait::{waitpid, WaitPidFlag as WF, WaitStatus as WS};
use nix::unistd::Pid;
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;

lazy_static! {
    static ref REAP_MAP: Mutex<HashMap<i32, i32>> = Mutex::new(HashMap::new());
    static ref STOP_MAP: Mutex<HashSet<i32>> = Mutex::new(HashSet::new());
    static ref CONT_MAP: Mutex<HashSet<i32>> = Mutex::new(HashSet::new());
    static ref KILL_MAP: Mutex<HashMap<i32, i32>> = Mutex::new(HashMap::new());
}

pub fn killed_map_insert(pid: i32, sig: i32) {
    if let Ok(mut m) = KILL_MAP.try_lock() {
        m.insert(pid, sig);
    }
}

pub fn killed_map_pop(pid: i32) -> Option<i32> {
    if let Ok(mut m) = KILL_MAP.try_lock() {
        m.remove(&pid)
    } else {
        None
    }
}

pub fn insert_cont_map(pid: i32) {
    if let Ok(mut m) = CONT_MAP.try_lock() {
        m.insert(pid);
    }
}

pub fn pop_cont_map(pid: i32) -> bool {
    match CONT_MAP.try_lock() {
        Ok(mut m) => m.remove(&pid),
        Err(_) => false,
    }
}

pub fn insert_stopped_map(pid: i32) {
    if let Ok(mut m) = STOP_MAP.try_lock() {
        m.insert(pid);
    }
}

pub fn pop_stopped_map(pid: i32) -> bool {
    match STOP_MAP.try_lock() {
        Ok(mut m) => m.remove(&pid),
        Err(_) => false,
    }
}

pub fn insert_reap_map(pid: i32, status: i32) {
    if let Ok(mut m) = REAP_MAP.try_lock() {
        m.insert(pid, status);
    }
}

pub fn pop_reap_map(pid: i32) -> Option<i32> {
    match REAP_MAP.try_lock() {
        Ok(mut m) => m.remove(&pid),
        Err(_) => None,
    }
}

pub fn block_signals() {
    let mut sigset = signal::SigSet::empty();
    sigset.add(signal::SIGCHLD);
    match signal::sigprocmask(signal::SigmaskHow::SIG_BLOCK, Some(&sigset), None) {
        Ok(_) => {}
        Err(e) => {
            log!("sigprocmask block error: {:?}", e);
        }
    }
}

pub fn unblock_signals() {
    let mut sigset = signal::SigSet::empty();
    sigset.add(signal::SIGCHLD);
    match signal::sigprocmask(signal::SigmaskHow::SIG_UNBLOCK, Some(&sigset), None) {
        Ok(_) => {}
        Err(e) => {
            log!("sigprocmask unblock error: {:?}", e);
        }
    }
}

#[allow(unreachable_patterns)]
pub extern "C" fn handle_sigchld(_sig: i32) {
    let saved_errno = errno();
    let options = Some(WF::WUNTRACED | WF::WNOHANG | WF::WCONTINUED);
    loop {
        match waitpid(Pid::from_raw(-1), options) {
            Ok(WS::Exited(pid, status)) => {
                insert_reap_map(i32::from(pid), status);
            }
            Ok(WS::Stopped(pid, _sig)) => {
                insert_stopped_map(i32::from(pid));
            }
            Ok(WS::Continued(pid)) => {
                // NOTE: SIGCHLD generated by SIGCONT is not reliable
                // on Mac (both for signal handler or sync waitpid).
                insert_cont_map(i32::from(pid));
            }
            Ok(WS::Signaled(pid, sig, _core_dumped)) => {
                killed_map_insert(i32::from(pid), sig as i32);
            }
            Ok(WS::StillAlive) => {
                break;
            }
            Ok(_others) => {
                // log!("sigchld others: {:?}", _others);
            }
            Err(e) => {
                if e == nix::Error::ECHILD {
                    break;
                }

                log!("chld waitpid error: {:?}", e);
                break;
            }
        }
    }

    set_errno(saved_errno);
}

pub fn setup_sigchld_handler() {
    let sigset = signal::SigSet::empty();
    let handler = signal::SigHandler::Handler(handle_sigchld);
    // automatically restart system calls interrupted by this signal handler
    let flags = signal::SaFlags::SA_RESTART;
    let sa = signal::SigAction::new(handler, flags, sigset);
    unsafe {
        match signal::sigaction(signal::SIGCHLD, &sa) {
            Ok(_) => {}
            Err(e) => {
                log!("sigaction error: {:?}", e);
            }
        }
    }
}
